#include "Simulator.h"
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

// --------------
// Public Methods
// --------------

Simulator::Simulator() {
	processes.clear();
	processDefs.clear();
	procResults.clear();
	processCount = 0;
}



int Simulator::LoadScript(const char * filename) {
	ifstream f;
	istringstream ss;
	map <unsigned long, vector <Process *> *>::iterator procsIter;
	map <string, Process *>::iterator defsIter;
	Process * p, * def;
	string line;
	unsigned long pid;

	// Clear out the processes
	ClearProcesses();
	ClearResults();

	// Open up the process script file
	f.open(filename);
	if (f.fail()) {
		perror(filename);
		return 1;
	}

	// Read in each process definition line
	while (getline(f, line)) {
		
		// Ignore empty lines and comments
		if ((line.empty()) || (line[0] == '#'))
			continue;

		// When the delimter for the DEFINE/ISSUE boundary is encountered switch modes
		if (line.compare("BEGIN_ISSUE") == 0)
			break;

		// Allocate a new process and parse the line into its attributes
		def = new Process();
		ss.clear();
		ss.str(line);
		if (!(ss >> def->name >> def->executionTime >> def->blockPeriod >> def->blockTime >> def->priority >> def->deadline)) {
			cerr << "Bad process definition line: \"" << line << "\"" << endl;
			delete def;
			ClearDefinitions();
			f.close();
			return 1;
		}
		
		// Ignore tasks that take 0 time
		if (def->executionTime == 0) {
			delete def;
			continue;
		}

		// No blocking occurs
		if ((def->blockTime == 0) || (def->blockPeriod == 0))
			def->blockTime = def->blockPeriod = 0;

		// Add the definition to the map
		if (processDefs.insert(make_pair(def->name, def)).second == false) {
			cerr << "Multiple definitions of process named \"" << def->name << "\"" << endl;
			delete def;
			ClearDefinitions();
			f.close();
			return 1;
		}
	}

	// Read in each process issue line
	pid = 1;
	while (getline(f, line)) {
		
		// Ignore empty lines and comments
		if ((line.empty()) || (line[0] == '#'))
			continue;
	
		// Allocate a new process to issue
		p = new Process();
		p->id = pid;
		ss.clear();
		ss.str(line);
		if (!(ss >> p->issueTime >> p->name)) {
			cerr << "Bad process issue line: \"" << line << "\"" << endl;
			delete p;
			ClearDefinitions();
			ClearProcesses();
			ClearResults();
			f.close();
			return 1;
		}

		// Get the definition for the process
		defsIter = processDefs.find(p->name);
		if (defsIter == processDefs.end()) {
			cerr << "No definition for process named \"" << p->name << "\"" << endl;
			delete p;
			ClearDefinitions();
			ClearProcesses();
			ClearResults();
			f.close();
			return 1;
		}
		def = defsIter->second;

		// Copy over the definition attributes
		p->executionTime = def->executionTime;
		p->blockPeriod = def->blockPeriod;
		p->blockTime = def->blockTime;
		p->priority = def->priority;
		p->deadline = def->deadline;

		// Convert deadline from relative to absolute
		if(p->deadline == 0)
			p->deadline = ULONG_MAX;
		else
			p->deadline = p->issueTime + p->deadline;

		// Add the process to the map of processes
		procsIter = processes.find(p->issueTime);
		if (procsIter == processes.end())
			procsIter = processes.insert(make_pair(p->issueTime, new vector <Process *>())).first;
		procsIter->second->push_back(p);

		// Add the process to the Results map if it is not already in it
		if (procResults.find(p->name) == procResults.end())
			procResults.insert(make_pair(p->name, new Results));

		// Increment the number of processes and the current process id
		processCount++;
		pid++;
	}

	// Close the process script file and clear the definitions
	ClearDefinitions();
	f.close();

	return 0;
}



void Simulator::Run(Scheduler * schedArg, bool traceModeEnabledArg, bool preemptBlocksEnabled, unsigned long processSwitchCyclesArg) {
	Process * currentProcess;

	// Initialize variables for this run
	currentProcess = NULL;
	sched = schedArg;
	traceModeEnabled = traceModeEnabledArg;
	processSwitchCycles = processSwitchCyclesArg;
	InitializeRun();

	// Loop until the number of processes that have finished executing is equal to the total
	while (finishedCount != processCount) {

		// Issue all processes that are to be issued on the current clock cycle
		IssueProcesses();

		// Check if some blocking processes have finished, and allow them to be scheduled again
		UnblockProcesses();

		// If there is not an active process, request the next process to execute from the scheduler
		if (currentProcess == NULL) {
			currentProcess = sched->GetNextProcess();
			if (currentProcess != NULL) {
				
				// If new process is different then it was a process switch
				if(currentProcess != lastActiveProcess) {
					ProcessSwitch();
				}

				PrintTrace("Started executing %lu on clock cycle %lu\n", currentProcess->id, clockCycle);
				lastActiveProcess = currentProcess;
				currentProcess->timeQueued += clockCycle - currentProcess->inactiveStart;
			
			// No active process, increment to the next clock cycle
			} else {
				clockCycle++;
				continue;	
			}
		}
		// Otherwise check if a higher priority process should preempt the current process
		else if (sched->ShouldPreempt(currentProcess)) {
			PrintTrace("Preempted %lu on clock cycle %lu (higher priority process)\n", currentProcess->id, clockCycle);
			currentProcess->inactiveStart = clockCycle;
			sched->AddProcess(currentProcess);
			currentProcess = sched->GetNextProcess();
			ProcessSwitch();
			PrintTrace("Started executing %lu on clock cycle %lu\n", currentProcess->id, clockCycle);
			lastActiveProcess = currentProcess;
			currentProcess->timeQueued += clockCycle - currentProcess->inactiveStart;
		}

		// There is an active process
		// Before increasing the clock cycles, check if the process blocks during this clock cycle
		UpdateBlockingStatus(currentProcess);
		
		// Increment to the next clock cycle and update its progress
		clockCycle++;
		if (currentProcess->timeAllotted != 0) {
			currentProcess->timeUsed++;
		}

		// Non-blocking, it had a productive clock cycle
		if(currentProcess->bllBlocking == false) {
			currentProcess->timeTotalProductive++;
			currentProcess->productiveCount++;
			countActiveCycles++;
			
			// Check if the current process is finished or can be preempted
			if (currentProcess->timeTotalProductive == currentProcess->executionTime) {
				ProcessFinished(currentProcess);
				currentProcess = NULL;
			}
			else if ((currentProcess->timeAllotted != 0) && (currentProcess->timeUsed == currentProcess->timeAllotted)) {
				PrintTrace("Preempted %lu on clock cycle %lu (out of time)\n", currentProcess->id, clockCycle);
				currentProcess->inactiveStart = clockCycle;
				sched->AddProcess(currentProcess);
				currentProcess = NULL;
			}
		// Blocking
		} else {
			countBlockedCycles++;
			
			// Check if it can be preempted
			if ((currentProcess->timeAllotted != 0) && (currentProcess->timeUsed == currentProcess->timeAllotted)) {
				PrintTrace("Preempted %lu on clock cycle %lu (out of time)\n", currentProcess->id, clockCycle);
				blockingProcesses.push_back(currentProcess);
				currentProcess = NULL;
			}
			else if (preemptBlocksEnabled == true) {
				PrintTrace("Preempted %lu on clock cycle %lu (blocking)\n", currentProcess->id, clockCycle);
				blockingProcesses.push_back(currentProcess);
				currentProcess = NULL;
			}
		}
	}

	// When finished calculate and print results
	PrintResults();
}



// ---------------
// Private Methods
// ---------------

// Called to clear the processes map
void Simulator::ClearProcesses() {
	map <unsigned long, vector <Process *> *>::iterator procsIter;
	unsigned long i;

	for (procsIter = processes.begin(); procsIter != processes.end(); ++procsIter) {
		for (i = 0; i < procsIter->second->size(); ++i) {
			delete procsIter->second->at(i);
		}
		delete procsIter->second;
	}
	processCount = 0;
	processes.clear();
}

// Called to clear the process definitions map
void Simulator::ClearDefinitions() {
	map <string, Process *>::iterator defsIter;

	for (defsIter = processDefs.begin(); defsIter != processDefs.end(); ++defsIter)
		delete defsIter->second;

	processDefs.clear();
}

// Called to clear the process results map
void Simulator::ClearResults() {
	map <string, Results *>::iterator resIter;

	for (resIter = procResults.begin(); resIter != procResults.end(); ++resIter)
		delete resIter->second;

	procResults.clear();
}

// Called when Run() starts, initializes variables
void Simulator::InitializeRun() {
	map <string, Results *>::iterator resIter;
	Results * r;

	finishedCount = 0;
	clockCycle = 0;
	countBlockedCycles = 0;
	countActiveCycles = 0;
	countProcessSwitchCycles = 0;
	lastActiveProcess = NULL;

	for (resIter = procResults.begin(); resIter != procResults.end(); ++resIter) {
		r = resIter->second;
		r->count = 0;
		r->turnaroundAverage = r->turnaroundWorst = 0;
		r->queuingAverage = r->queuingWorst = 0;
		r->countMetDeadlines = r->countMissedDeadlines = 0;
	}

}

// Called when Run() ends, calculates and prints results
void Simulator::PrintResults() {
	double cpuUtilization; // % of time cpu is active (not blocked)
	double percentSwitching; // % of cycles spent in a process switch
	double percentBlocked; // % of cycles with the active process blocking
	Results totalResults, * r;
	map <string, Results *>::iterator resIter;

	// Calculate results
	cpuUtilization = (double)countActiveCycles/clockCycle*100;
	percentBlocked = (double)countBlockedCycles/clockCycle*100;
	percentSwitching = (double)countProcessSwitchCycles/clockCycle*100;
	totalResults.turnaroundAverage = 0;
	totalResults.turnaroundWorst = 0;
	totalResults.queuingAverage = 0;
	totalResults.queuingWorst = 0;
	totalResults.countMetDeadlines = 0;
	for (resIter = procResults.begin(); resIter != procResults.end(); ++resIter) {
		r = resIter->second;

		totalResults.turnaroundAverage += r->turnaroundAverage;
		r->turnaroundAverage /= r->count;
		if (r->turnaroundWorst > totalResults.turnaroundWorst) {
			totalResults.turnaroundWorst = r->turnaroundWorst;
		}
		
		totalResults.queuingAverage += r->queuingAverage;
		r->queuingAverage /= r->count;
		if (r->queuingWorst > totalResults.queuingWorst) {
			totalResults.queuingWorst = r->queuingWorst;
		}

		totalResults.countMetDeadlines += r->countMetDeadlines;
		r->percentDeadlinesMet = (double)r->countMetDeadlines / r->count * 100;
	}
	totalResults.turnaroundAverage /= processCount;
	totalResults.queuingAverage /= processCount;
	totalResults.percentDeadlinesMet = (double)totalResults.countMetDeadlines/processCount*100;

	// Print results
	printf("\n");
	printf("-----------Results-----------\n");
	printf("Runtime: %lu cycles\n", clockCycle);
	printf("CPU utlization: %.2lf %%\n", cpuUtilization);
	printf("Percent of cycles active process blocked: %.2lf %%\n", percentBlocked);
	printf("Percent of cycles spent process switching: %.2lf %%\n", percentSwitching);
	
	printf("Queuing times:\n");
	printf("\tTotal:\n");
	printf("\t\tAverage: %.2lf\n", totalResults.queuingAverage);
	printf("\t\tWorst: %lu\n", totalResults.queuingWorst);
	for (resIter = procResults.begin(); resIter != procResults.end(); ++resIter) {
		r = resIter->second;
		printf("\t\"%s\" Processes:\n", resIter->first.c_str());
		printf("\t\tAverage: %.2lf\n", r->queuingAverage);
		printf("\t\tWorst: %lu\n", r->queuingWorst);
	}

	printf("Turnaround times:\n");
	printf("\tTotal:\n");
	printf("\t\tAverage: %.2lf\n", totalResults.turnaroundAverage);
	printf("\t\tWorst: %lu\n", totalResults.turnaroundWorst);
	for (resIter = procResults.begin(); resIter != procResults.end(); ++resIter) {
		r = resIter->second;
		printf("\t\"%s\" Processes:\n", resIter->first.c_str());
		printf("\t\tAverage: %.2lf\n", r->turnaroundAverage);
		printf("\t\tWorst: %lu\n", r->turnaroundWorst);
	}

	printf("Deadlines met:\n");
	printf("\tTotal: %.2lf %%\n", totalResults.percentDeadlinesMet);
	for (resIter = procResults.begin(); resIter != procResults.end(); ++resIter) {
		r = resIter->second;
		printf("\t\"%s\" Processes: %.2lf %%\n", resIter->first.c_str(), r->percentDeadlinesMet);
	}
	printf("-----------------------------\n");
	printf("\n");

}

// Called during Run() for each clockCycle; issues processes
// that are to be issued on the current value of clockCycle
void Simulator::IssueProcesses() {
	int i;
	map <unsigned long, vector <Process *> *>::iterator iter;
	Process * p;
	
	iter = processes.find(clockCycle);
	if (iter != processes.end()) {
		for (i = 0; i < iter->second->size(); i++) {
			p = iter->second->at(i);
			p->timeAllotted = 0;
			p->timeTotalProductive = 0;
			p->productiveCount = 0;
			p->bllBlocking = false;
			p->timeQueued = 0;
			p->inactiveStart = clockCycle;
			procResults[p->name]->count += 1;
			sched->AddProcess(p);
			PrintTrace("Issued process %lu on clock cycle %lu.\n", p->id, clockCycle);
		}
	}
}

// Called to update the blocking status for the current active process
// and for the currently blocking processes
void Simulator::UpdateBlockingStatus(Process * p){
	// The process is not able to block
	if(p->blockTime == 0) {
		return;
	}

	// It was blocking, see if it still is
	if(p->bllBlocking == true) {

		if(clockCycle - p->blockStartCycle >= p->blockTime) {
			PrintTrace("Process %lu has finished blocking on clock cycle %lu.\n", p->id, clockCycle);
			p->productiveCount = 0;
			p->inactiveStart = clockCycle;
			p->bllBlocking = false;
		}

	// Else check if it starts blocking
	} else if(p->productiveCount == p->blockPeriod) {
		PrintTrace("Process %lu has started blocking on clock cycle %lu.\n", p->id, clockCycle);
		p->bllBlocking = true;
		p->blockStartCycle = clockCycle;
	}
}


// Called during Run() for each clockCycle; checks if any processes can be
// unblocked and re-added to the scheduler
void Simulator::UnblockProcesses(){
	int i;
	list <Process *>::iterator it;
	Process * currentProcess;

	for(it = blockingProcesses.begin(); it != blockingProcesses.end(); ) {

		currentProcess = *it;

		UpdateBlockingStatus(currentProcess);

		// No longer blocking, it can be scheduled again
		if(currentProcess->bllBlocking == false) {
			sched->AddProcess(currentProcess);

			// Remove from list of blocking processes
			it = blockingProcesses.erase(it);
		}
		else {
			it++;
		}
	}
}

// Called when we switch active processes; this simulates a delay.
// During a process switch, we still issue processes and update the clock.
void Simulator::ProcessSwitch() {
	int i;

	if (processSwitchCycles == 0) {
		return;
	}

	PrintTrace("Process switch (%lu cycle delay)\n", processSwitchCycles);

	for (i = 0; i < processSwitchCycles; i++) {
		clockCycle++;
		IssueProcesses();
		UnblockProcesses();
	}
	countProcessSwitchCycles += processSwitchCycles;
}

// Called when a process is finished executing
void Simulator::ProcessFinished(Process * p) {
	Results * r;

	// Calculate turnaround time
	p->timeTurnaround = clockCycle - p->issueTime;
	PrintTrace("Finished executing %lu on clock cycle %lu (turnaround time = %lu cycles, queuing time = %lu cycles)\n", p->id, clockCycle, p->timeTurnaround, p->timeQueued);

	// Get the Results struct for the name of this process
	r = procResults[p->name];

	// Update average and worst turnaround times
	if (p->timeTurnaround > r->turnaroundWorst) {
		r->turnaroundWorst = p->timeTurnaround;
	}
	r->turnaroundAverage += p->timeTurnaround;

	// Update average and worst queuing times
	if (p->timeQueued > r->queuingWorst) {
		r->queuingWorst = p->timeQueued;
	}
	r->queuingAverage += p->timeQueued;

	// Update deadline count based on whether it was met or not
	if (clockCycle <= p->deadline) {
		r->countMetDeadlines++;
	} else {
		r->countMissedDeadlines++;
	}

	finishedCount++;
}

// Called whenever a line of the execution trace is to be printed.
// Only prints if trace mode is enabled.
void Simulator::PrintTrace(const char * format, ...) {
	va_list args;

	va_start(args, format);
	if (traceModeEnabled)
		vprintf(format, args);
	va_end(args);
}
